package civaid;

import java.util.logging.Logger;

import javax.jdo.JDOException;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

public class JDODatabaseService {

	protected static Logger log = Logger.getLogger(DatabaseService.class.getName());

	private static final PersistenceManagerFactory PMF = JDOHelper.getPersistenceManagerFactory("transactional");

	private static PersistenceManagerFactory getPMF() {
		return PMF;
	}

	protected JDODatabaseService() {
	}

	static class DBException extends RuntimeException {

		public DBException(String msg, Throwable e) {
			super(msg, e);
		}

		private static final long serialVersionUID = -1269815629872840899L;

	}

	static abstract class DBOperation<T> {

		private boolean useTransaction = false;
		private PersistenceManager pm;

		public DBOperation(boolean useTransaction) {
			this.useTransaction = useTransaction;
		}

		protected void start() {
			if (useTransaction) {
				pm.currentTransaction().begin();
				log.fine("started transaction");
			}

		}

		protected void commit() {
			if (useTransaction && pm.currentTransaction().isActive()) {
				pm.currentTransaction().commit();
				log.fine("transaction committed");
			}
		}

		protected void rollback() {
			if (useTransaction && pm.currentTransaction().isActive()) {
				pm.currentTransaction().rollback();
				log.fine("transaction rolledback");
			}
		}

		public T execute(JDODatabaseService db, PersistenceManager pm) {

			this.pm = pm;
			int retryCount = 0;
			Throwable exception = null;

			try {

				while (retryCount < 3) {
					try {
						log.fine("db operation started");

						start();

						T result = doIt(db, pm);

						commit();

						return result;

					} catch (JDOException e) {
						log.fine("db operation failed: " + e.getMessage() + ", retrying (" + retryCount + ")");
						retryCount++;
						exception = e;

						rollback();

					} catch (Throwable e) {
						log.fine("db operation failed: " + e.getMessage());

						rollback();
						throw new DBException("db operation failed", e);
					}
				}

			} finally {
				log.fine("db operation completed");
				if (useTransaction) {
					pm.close();
					log.fine("pm closed");
				}
			}

			throw new DBException("db operation failed (retried)", exception);

		}

		protected abstract T doIt(JDODatabaseService db, PersistenceManager pm);

	}

	protected Object executeOperation(DBOperation<?> op) {
		return executeOperation(op, getPMF().getPersistenceManager());
	}

	protected Object executeOperation(DBOperation<?> op, PersistenceManager persistenceManager) {
		return op.execute(this, persistenceManager);

	}

}
